﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using GraphosData;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using System.ComponentModel;

namespace GraphosControls
{

    /// <summary>
    /// Interaction logic for GraphViewer.xaml
    /// </summary>
    public partial class GraphViewer : UserControl
    {
        public GraphViewer()
        {
            InitializeComponent();
        }

        #region Dependecy Properties

        /// <summary>
        /// Gets or sets the current selected node
        /// </summary>
        public Node RootNode
        {
            get { return (Node)GetValue(SelectedNodeProperty); }
            set { SetValue(SelectedNodeProperty, value); }
        }
        public static readonly DependencyProperty SelectedNodeProperty =
                  DependencyProperty.Register("RootNode", typeof(Node), typeof(GraphViewer),
                  new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnSelectedNodeChanged)));
        private static void OnSelectedNodeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            GraphViewer g = sender as GraphViewer;
            VisualNode o;
            if (args.OldValue != null && g.m_nodeToVisualNode.TryGetValue(args.OldValue as Node, out o))
                o.IsSelected = false;

            if (args.NewValue == null)
                return;

            VisualNode n;
            if (g.TryAddVisualNode(args.NewValue as Node, out n))
            {
                n.Position = new Point(Rand.Double * g.MainCanvas.ActualWidth, Rand.Double * g.MainCanvas.ActualHeight);
            }
            n.IsSelected = true;
            g.CalculateNodePositions(n);
        }

        /// <summary>
        /// Gets or set the current Graph object which populates the nodes
        /// </summary>
        public Graph Graph
        {
            get { return (Graph)GetValue(GraphProperty); }
            set { SetValue(GraphProperty, value); }
        }
        public static readonly DependencyProperty GraphProperty =
                  DependencyProperty.Register("Graph", typeof(Graph), typeof(GraphViewer), 
                  new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));

        public GraphViewerAnimation Anim
        {
            get { return (GraphViewerAnimation)GetValue(AnimProperty); }
            set { SetValue(AnimProperty, value); }
        }
        public static readonly DependencyProperty AnimProperty =
            DependencyProperty.Register("Anim", typeof(GraphViewerAnimation), typeof(GraphViewer), 
            new FrameworkPropertyMetadata(new GraphViewerAnimation(), FrameworkPropertyMetadataOptions.None));

        #endregion

        #region Events

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            // scroll stuff will start here when i get to it //
            base.OnMouseLeftButtonDown(e);
        }

        protected override void OnInitialized(EventArgs e)
        {
            MainCanvas.SizeChanged += new SizeChangedEventHandler(MainCanvas_SizeChanged);
            Anim.TransitionAnimationComplete += new GraphViewerAnimation.AnimationCompleteEventHandler(m_Anim_TransitionAnimationComplete);
            Anim.PositionAnimationComplete += new RoutedEventHandler(Anim_PositionAnimationComplete);
            base.OnInitialized(e);
        }

        void Anim_PositionAnimationComplete(object sender, RoutedEventArgs e)
        {
            // add all the edge labels from the root node
            // TODO: this can be modified to be all labels for all edges...but it gets bit crowded on screen
            VisualNode root = m_nodeToVisualNode[RootNode];
            foreach (var edge in m_visualNodesToVisualEdge[root].Values)
            {
                if (!MainCanvas.Children.Contains(edge.LabelTextBlock))
                {
                    MainCanvas.Children.Add(edge.LabelTextBlock);
                    m_edgeLabels.Add(edge.LabelTextBlock);
                    Anim.ApplyCreateLabelAnimation(edge.LabelTextBlock);
                }
            }
        }

        void m_Anim_TransitionAnimationComplete(object sender, TransitionAnimationCompleteEventArgs e)
        {
            foreach (UIElement elem in e.ElemetsDoneAnimating)
            {
                MainCanvas.Children.Remove(elem);
            }
        }

        private void MainCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (e.NewSize == e.PreviousSize)
                return;

            if (RootNode != null)
            {
                Anim.Start(m_nodeToVisualNode[RootNode], e.NewSize.Width, e.NewSize.Height);
            }
        }

        //private void VisualNode_Click(object sender, RoutedEventArgs e)
        //{
        //    VisualNode vn = sender as VisualNode;
        //    if (vn == null)
        //        return;
        //    SelectedNode = vn.Node;
        //}

        private void VisualNode_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            VisualNode vn = sender as VisualNode;
            if (vn == null)
                return;

            RootNode = vn.Node;
            e.Handled = true;
        }
        private void VisualNode_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            VisualNode vn = sender as VisualNode;
            if (vn == null)
                return;

            Anim.DragNode = vn;
            Anim.DragPosition = e.GetPosition(MainCanvas);
            e.Handled = true;
        }
        protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
        {
            base.OnMouseRightButtonUp(e);
            if (Anim.DragNode != null && Anim.DragNode.IsMouseCaptured)
            {
                Anim.DragPosition = e.GetPosition(MainCanvas);
                Anim.DragNode = null;
            }
        }
      
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            if (e.RightButton == MouseButtonState.Pressed && Anim.DragNode != null)
            {
                Anim.DragPosition = e.GetPosition(MainCanvas);
            }
        }
        private void MainScrollViewer_MouseMove(object sender, MouseEventArgs e)
        {
            //if (e.LeftButton == MouseButtonState.Pressed)
            //{
            //    m_Anim.MoveMouseCharge(Mouse.GetPosition(MainCanvas));
            //    m_Anim.MouseRepulse = true;
            //    m_Anim.MouseAttract = false;

                
            //}
            //else if (e.RightButton == MouseButtonState.Pressed)
            //{
            //    m_Anim.MoveMouseCharge(Mouse.GetPosition(MainCanvas));
            //    m_Anim.MouseAttract = true;
            //    m_Anim.MouseRepulse = false;
            //}
            //else
            //{
            //    m_Anim.MouseAttract = false;
            //    m_Anim.MouseRepulse = false;
            //}
            //if (!m_Anim.IsInitialized && SelectedNode != null)
            //{
            //    m_Anim.StartPositionAnimation(m_nodeToVisualNode[SelectedNode], MainCanvas.ActualWidth, MainCanvas.ActualHeight);
            //}
        }

        #endregion

        #region Node Positioning

        // remove ande add all the appropriate nodes, and begin the positioning and Animation process
        private void CalculateNodePositions(VisualNode visualRootNode)
        {

            foreach (var label in m_edgeLabels)
            {
                Anim.ApplyDestroyLabelAnimation(label);
            }
            m_edgeLabels.Clear();

            // the list of nodes which need to be removed
            List<VisualNode> visualNodesToRemove = new List<VisualNode>();
            foreach (var pair in m_nodeToVisualNode)
            {
                if (!visualRootNode.Node.AdjacentNodes.Contains(pair.Key) && pair.Key != visualRootNode.Node)
                    visualNodesToRemove.Add(pair.Value);
            }

           
            // remove them visual edges that aren't needed anymore
            foreach (var from in visualNodesToRemove)
            {
                foreach (var to in from.AdjacentVisualNodes)
                {
                    RemoveVisualEdge(from, to);
                }
                RemoveVisualNode(from);
            }

            // add in the new nodes on tope of the nodes which are being removed
            foreach (Node node in visualRootNode.Node.AdjacentNodes.Except(m_nodeToVisualNode.Keys))
            {
                VisualNode visualNode;
                if (TryAddVisualNode(node, out visualNode))
                {
                    // TODO initial Positioning can go here
                    visualNode.Position = new Point(Rand.Double * MainCanvas.ActualWidth, MainCanvas.ActualHeight - Rand.Double * MainCanvas.ActualHeight);
                }
            }

            // set up the visual node adjacency lists
            foreach (VisualNode visualNode in m_nodeToVisualNode.Values)
            {
                List<VisualNode> listAdjacentVisualNodes = new List<VisualNode>();
                foreach (Node node in visualNode.Node.AdjacentNodes)
                {
                    VisualNode toVisualNode;
                    if (m_nodeToVisualNode.TryGetValue(node, out toVisualNode))
                    {
                        listAdjacentVisualNodes.Add(toVisualNode);
                    }
                }
                visualNode.AdjacentVisualNodes = listAdjacentVisualNodes;
            }

            // add the appropriate edges
            List<VisualEdge> edges = new List<VisualEdge>();
            foreach (var from in visualRootNode.AdjacentVisualNodes)
            {
                foreach (var to in from.AdjacentVisualNodes.Where(n => n != visualRootNode))
                {
                    VisualEdge visualEdge;
                    TryAddVisualEdge(from, to, VisualEdgeType.Secondary, out visualEdge);
                    edges.Add(visualEdge);
                }
                VisualEdge e;
                TryAddVisualEdge(visualRootNode, from, VisualEdgeType.Primary, out e);
                edges.Add(e);
            }

            Anim.SetEdges(edges);
            // get the Animation started off right!
            Anim.Start(visualRootNode, MainCanvas.ActualWidth, MainCanvas.ActualHeight);
        }

        #endregion

        #region Private Helpers

        // attempt to add a VisualNode to the viewer
        // visualNode will return a valid VisualNode always
        private bool TryAddVisualNode(Node node, out VisualNode visualNode)
        {
            if (!m_nodeToVisualNode.TryGetValue(node, out visualNode))
            {
                visualNode = new VisualNode(node);
                m_nodeToVisualNode.Add(node, visualNode);
                MainCanvas.Children.Add(visualNode);
                Anim.ApplyCreateNodeAnimation(visualNode);
                return true;
            }
            return false;
        }

        // remove a visualnode (this applies its destroy animation)
        private void RemoveVisualNode(VisualNode visualNode)
        {
            m_nodeToVisualNode.Remove(visualNode.Node);
            Anim.ApplyDestroyNodeAnimation(visualNode);
        }

        // try to add a visual edge between two visual nodes (visualEdge will always contain a non null value)
        private bool TryAddVisualEdge(VisualNode from, VisualNode to, VisualEdgeType type, out VisualEdge visualEdge)
        {
            // NOTE: this function doesn't actually return false right now

            // if edge goes from--to, or to--from don't add it again
            if (!m_visualNodesToVisualEdge.ContainsKey(from))
            {
                m_visualNodesToVisualEdge[from] = new Dictionary<VisualNode, VisualEdge>();
            }

            if (m_visualNodesToVisualEdge.ContainsKey(to) &&
                m_visualNodesToVisualEdge[to].TryGetValue(from, out visualEdge))
            {
                visualEdge.SwitchDirection();
                m_visualNodesToVisualEdge[to].Remove(from);

                if (m_visualNodesToVisualEdge[to].Count == 0)
                    m_visualNodesToVisualEdge.Remove(to);

                m_visualNodesToVisualEdge[from][to] = visualEdge;
            }
            else if (!m_visualNodesToVisualEdge[from].TryGetValue(to, out visualEdge))
            {
                visualEdge = new VisualEdge(from, to);
                visualEdge.CenterNode = new VisualNode();
                visualEdge.ControlNodeOne = new VisualNode();
                visualEdge.ControlNodeTwo = new VisualNode();
                m_visualNodesToVisualEdge[from][to] = visualEdge;
                MainCanvas.Children.Add(visualEdge);
                Anim.ApplyCreateEdgeAnimation(visualEdge);
            }

            visualEdge.Type = type;
            visualEdge.UpdateStyles();

            return true;
        }

        // remove the edge from the specified visual nodes
        private void RemoveVisualEdge(VisualNode from, VisualNode to)
        {

            // remove edges which go from -- to, or to -- from
            VisualEdge visualEdge = null;
            if (m_visualNodesToVisualEdge.ContainsKey(from) &&
                    m_visualNodesToVisualEdge[from].TryGetValue(to, out visualEdge))
            {
                m_visualNodesToVisualEdge[from].Remove(to);
                if (m_visualNodesToVisualEdge[from].Count == 0)
                    m_visualNodesToVisualEdge.Remove(from);
                Anim.ApplyDestroyEdgeAnimation(visualEdge);
            }
            if (m_visualNodesToVisualEdge.ContainsKey(to) &&
                  m_visualNodesToVisualEdge[to].TryGetValue(from, out visualEdge))
            {
                m_visualNodesToVisualEdge[to].Remove(from);
                if (m_visualNodesToVisualEdge[to].Count == 0)
                    m_visualNodesToVisualEdge.Remove(to);
                Anim.ApplyDestroyEdgeAnimation(visualEdge);
            }
        }

        #endregion

        #region Fields

        private Dictionary<Node, VisualNode> m_nodeToVisualNode =
             new Dictionary<Node, VisualNode>();

        private Dictionary<VisualNode, Dictionary<VisualNode, VisualEdge>> m_visualNodesToVisualEdge =
                new Dictionary<VisualNode, Dictionary<VisualNode, VisualEdge>>();

        private List<TextBlock> m_edgeLabels = new List<TextBlock>();

        #endregion

       

        

    }

    #region Internal helper classes

  

    /// <summary>
    /// An internal random class for easy random number access
    /// </summary>
    internal static class Rand
    {
        public static double Double
        {
            get { return rand.NextDouble(); }
        }
        public static int Int(int from, int to)
        {
            return rand.Next(from, to);
        }
        static Random rand = new Random();
    }

    #endregion
}
